<!DOCTYPE html>
<html lang="en">
  
  <head>
    <meta charset="UTF-8">
    <title>Visual Mapping</title>
    <style>
      .g6-tooltip {
        border: 1px solid #e2e2e2;
        border-radius: 4px;
        font-size: 12px;
        color: #545454;
        background-color: rgba(255, 255, 255, 0.9);
        padding: 10px 8px;
        box-shadow: rgb(174, 174, 174) 0px 0px 10px;
      }
    </style>
  </head>
  
  <body>
    <div id="tip">节点：颜色 —— cluster；大小 —— value；形状 —— subCluster
      <br/>边：颜色 —— 起始点颜色；粗细 —— weight
    </div>
    <div id="mountNode"></div>
    <script src="../build/g6.js"></script>
    <script src="./assets/jquery-3.2.1.min.js"></script>
    <script>
      const colors = [ 'rgb(64, 174, 247)', 'rgb(108, 207, 169)', 'rgb(157, 223, 125)',
      'rgb(240, 198, 74)' ];
      const shapes = [ 'circle', 'rect' ];

      const data = {
        "nodes": [{
          "id": "0",
          "label": "0",
          "value": 10,
          "cluster": "a",
          "subCluster": "sa"
        },
        {
          "id": "1",
          "label": "1",
          "value": 20,
          "cluster": "b",
          "subCluster": "sb"
        },
        {
          "id": "2",
          "label": "2",
          "value": 5,
          "cluster": "a",
          "subCluster": "sa"
        },
        {
          "id": "3",
          "label": "3",
          "value": 10,
          "cluster": "a",
          "subCluster": "sa"
        },
        {
          "id": "4",
          "label": "4",
          "value": 12,
          "cluster": "c",
          "subCluster": "sb"
        },
        {
          "id": "5",
          "label": "5",
          "value": 18,
          "cluster": "c",
          "subCluster": "sa"
        },
        {
          "id": "6",
          "label": "6",
          "value": 3,
          "cluster": "c",
          "subCluster": "sa"
        },
        {
          "id": "7",
          "label": "7",
          "value": 7,
          "cluster": "b",
          "subCluster": "sa"
        },
        {
          "id": "8",
          "label": "8",
          "value": 21,
          "cluster": "d",
          "subCluster": "sb"
        },
        {
          "id": "9",
          "label": "9",
          "value": 9,
          "cluster": "d",
          "subCluster": "sb"
        }],
        "edges": [{
          "source": "0",
          "target": "1",
          "weight": 20,
        },
        {
          "source": "0",
          "target": "3",
          "weight": 12,
        },
        {
          "source": "0",
          "target": "4",
          "weight": 19,
        },
        {
          "source": "0",
          "target": "5",
          "weight": 2,
        },
        {
          "source": "0",
          "target": "7",
          "weight": 8,
        },
        {
          "source": "0",
          "target": "8",
          "weight": 14,
        },
        {
          "source": "0",
          "target": "9",
          "weight": 5,
        },
        {
          "source": "2",
          "target": "3",
          "weight": 20,
        },
        {
          "source": "4",
          "target": "5",
          "weight": 10,
        },
        {
          "source": "4",
          "target": "6",
          "weight": 11,
        }]
      };

      const graph = new G6.Graph({
        container: 'mountNode',
        width: 1000,
        height: 800,
        layout: {
          type: 'force',
          linkDistance: 100,
          nodeStrength: -30,
          edgeStrength: 0.1,
        },
        modes: {
          default: [{
            type: 'tooltip',
            formatText(model) {
              const text = 'NODE'
                + '<br/> id: ' + model.id
                + '<br/> cluster: ' + model.cluster
                + '<br/> subCluster: ' + model.subCluster
                + '<br/> value: ' + model.value;
              return text;
            },
            shouldUpdate: e => {
              return true;
            }
          },
          {
            type: 'edge-tooltip',
            formatText(model) {
              const text = 'EDGE'
                + '<br/> source: ' + model.source
                + '<br/> weight: ' + model.weight
                + '<br/> target: ' + model.target;
              return text;
            },
            shouldUpdate: e => {
              return true;
            }
          }]
        },
        defaultNode: {
          size: [10, 10],
          color: 'steelblue',
          style: {
            opacity: 0.8,
            lineWidth: 1,
            stroke: '#999'
          }
        },
        defaultEdge: {
          size: 1,
          color: '#e2e2e2',
          style: {
            opacity: 0.6,
            lineAppendWidth: 5
          }
        }
      });

      // mapping
      const edges = data.edges;
      const nodes = data.nodes;
      const nodeMap = new Map();
      const clusterMap = new Map();
      const subClusterMap = new Map();
      let clusterId = 0;
      let subClusterId = 0;
      nodes.forEach((node, i) => {
        nodeMap.set(node.id, node);
        // cluster
        if (node.cluster && clusterMap.get(node.cluster) === undefined) {
          clusterMap.set(node.cluster, clusterId);
          clusterId ++;
        }
        // subCluster
        if (node.subCluster && subClusterMap.get(node.subCluster) === undefined) {
          subClusterMap.set(node.subCluster, subClusterId);
          subClusterId ++;
        }
        const cid = clusterMap.get(node.cluster);
        const scid = subClusterMap.get(node.subCluster);
        if (node.style) {
          node.style.fill = colors[cid % colors.length];
        } else {
          node.style = {
            fill: colors[cid % colors.length]
          }
        }
        node.shape = shapes[scid % shapes.length];
      });

      // map the value to node size
      let maxEdgeWeight = -9999,
      minEdgeWeight = 9999;
      edges.forEach((edge, i) => {
        const source = nodeMap.get(edge.source);
        const target = nodeMap.get(edge.target);
        edge.color = source.style.fill;
        // lineWidth
        if (maxEdgeWeight < edge.weight) maxEdgeWeight = edge.weight;
        if (minEdgeWeight > edge.weight) minEdgeWeight = edge.weight;
      });

      // map the value to node size
      let maxNodeValue = -9999,
      minNodeValue = 9999;
      nodes.forEach(n => {
        if (maxNodeValue < n.value) maxNodeValue = n.value;
        if (minNodeValue > n.value) minNodeValue = n.value;
      });
      const nodeSizeRange = [10, 30];
      const nodeSizeDataRange = [minNodeValue, maxNodeValue];
      const lineWidthRange = [0.5, 3];
      const lineWidthDataRange = [minEdgeWeight, maxEdgeWeight];
      scaleNodeProp(nodes, 'size', 'value', nodeSizeDataRange, nodeSizeRange);
      scaleNodeProp(edges, 'style.lineWidth', 'weight', lineWidthDataRange, lineWidthRange);

      graph.data(data);
      graph.render();
      function scaleNodeProp(elements, propName, refPropName, dataRange, outRange) {
        const outLength = outRange[1] - outRange[0];
        const dataLength = dataRange[1] - dataRange[0];
        elements.forEach(n => {
          if (propName.split('.')[0] === 'style') {
            if (n.style) {
              n.style[propName.split('.')[1]] = (n[refPropName] - dataRange[0]) * outLength / dataLength + outRange[0];
            } else {
              n.style = {
                [propName.split('.')[1]]: (n[refPropName] - dataRange[0]) * outLength / dataLength + outRange[0]
              }
            }
          } else {
            n[propName] = (n[refPropName] - dataRange[0]) * outLength / dataLength + outRange[0];
          }
        });
      }
    </script>
  </body>

</html>